package com.twu.biblioteca;

import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import org.mockito.stubbing.OngoingStubbing;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.*;

/**
 * Created by jfulton on 14/01/2014.
 * Class containing test cases for Biblioteca class
 **/



public class BibliotestaIntegration {

    private OutputStream outContent = new ByteArrayOutputStream();
    private IOHandler io;
    private List<String> inputQueue;
    private LibraryManager libraryManager = new LibraryManager();
    private String testBook = "20,000 Leagues Under the Sea";

    @Before
    public void setup() throws Exception {
        System.setOut(new PrintStream(outContent));
        refreshIoMockAndBooks();
    }

    @Test
    public void testWelcome() throws Exception {

        refreshIoMockAndBooks();

        // Test that the welcome message displays when the program is started
        executeQueue();
        verify(io, times(1)).outputLine("Welcome to Biblioteca, distinguished user!");
    }

    @Test
    public void testListBooksOnStartup() throws Exception {
        refreshIoMockAndBooks();
        executeQueue();
        verify(io, times(1)).outputLine("Books Available:");
        verify(io, times(1)).outputLine(testBook);
    }

    @Test
    public void testDoNotListUnavailableBooks() throws Exception {
        refreshIoMockAndBooks();
        enqueueCheckoutBook(testBook);
        enqueueListBooks();
        executeQueue();

        verify(io, times(2)).outputLine("Books Available:");
        verify(io, times(1)).outputLine(testBook);
    }

    @Test
    public void testShowMenu() throws Exception {
        refreshIoMockAndBooks();
        when(io.readLine()).thenThrow(new IOException());
        libraryManager.displayMenu();
        verify(io, times(1)).outputLine("Please select from the following options:");

        verify(io, never()).outputLine("0 - View my details");
        verify(io, times(1)).outputLine("1 - List Available Books");
        verify(io, times(1)).outputLine("2 - List Available Movies");
        verify(io, times(1)).outputLine("3 - Login");
        verify(io, times(1)).outputLine("4 - Checkout Book");
        verify(io, times(1)).outputLine("5 - Checkout Movie");
        verify(io, times(1)).outputLine("6 - Return Book");
        // NOTE: The option for quitting isn't tested here as it's not in the spec.
    }

    @Test
    public void testInvalidMenuOptionSelected() throws Exception {
        refreshIoMockAndBooks();
        when(io.readLine())
                .thenReturn("I like turtles.")
                .thenThrow(new IOException());
        libraryManager.execute();
        verify(io, times(1)).outputLine("Select a valid option!");
    }

    @Test
    public void testSuccessfulCheckout() throws Exception {
        refreshIoMockAndBooks();
        enqueueCheckoutBook(testBook);
        executeQueue();
        verify(io, times(1)).outputLine("Thank you! Enjoy the book");
    }

    @Test
    public void testFailedCheckoutInvalidBook() throws Exception {
        refreshIoMockAndBooks();

        enqueueCheckoutBook("20,000 Lawyers Under the Sea");
        executeQueue();

        verify(io, times(1)).outputLine("That book is not available."); // Why hasn't someone written this book yet??
    }

    @Test
    public void testFailedCheckoutBookUnavailable() throws Exception {
        refreshIoMockAndBooks();

        enqueueCheckoutBook(testBook);
        enqueueCheckoutBook(testBook);

        executeQueue();

        verify(io, times(1)).outputLine("That book is not available.");
    }

    @Test
    public void testMovieCheckoutSuccessMessage() throws Exception {
        refreshIoMockAndBooks();

    }

    @Test
    public void testReturnSuccess() throws Exception {

        refreshIoMockAndBooks();

        enqueueCheckoutBook(testBook);
        enqueueReturnBook(testBook);

        executeQueue();

        verify(io, times(1)).outputLine("Thank you for returning the book.");


        // Ensure the book is now available

        libraryManager.listAvailableBooks();

        verify(io, times(2)).outputLine(testBook);
    }

    @Test
    public void testReturnFailed() throws Exception {
        refreshIoMockAndBooks();

        enqueueReturnBook("The Cat on the Mat");

        executeQueue();

        verify(io, times(1)).outputLine("That is not a valid book to return.");

        libraryManager.returnBookTitled("The Cat on the Mat");

        verify(io, times(2)).outputLine("That is not a valid book to return.");
    }



    @Test
    public void testListMovies() throws Exception {
        refreshIoMockAndBooks();
        enqueueListMovies();
        executeQueue();

        verify(io, times(1)).outputLine("Inception (2010), Christopher Nolan, unrated");
        verify(io, times(1)).outputLine("Batman Begins (2005), Christopher Nolan, unrated");
    }

    @Test
    public void testValidUserLogin() throws Exception {
        refreshIoMockAndBooks();
        enqueueLogin("123-4567", "password");
        enqueueLogin("234-5678", "password");
        executeQueue();

        verify(io, times(1)).outputLine("Welcome back, Bruce Wayne.");
        verify(io, times(1)).outputLine("Welcome back, Edward Nigma.");

    }

    @Test
    public void testUserInfoMenuOptionPresent() throws Exception {
        refreshIoMockAndBooks();
        enqueueLogin("123-4567", "password");
        executeQueue();

        verify(io, times(1)).outputLine("0 - View my details");
    }

    @Test
    public void testShowMyDetails() throws Exception {
        refreshIoMockAndBooks();
        enqueueLogin("123-4567", "password");
        enqueueShowMyDetails();
        executeQueue();
        verify(io, times(1)).outputLine("Name: Bruce Wayne");
        verify(io, times(1)).outputLine("Email: bwayne@waynetech.com");
        verify(io, times(1)).outputLine("Phone: (123) 456");
    }

    @After
    public void cleanup() {
        System.setOut(null);
    }

    private void refreshIoMockAndBooks() throws Exception {
        io = mock(IOHandler.class);
        inputQueue = new ArrayList<String>();
        libraryManager.setIoHandler(io);
        libraryManager.initBooks();
    }

    private void enqueueShowMyDetails() {
        inputQueue.add("0");
    }

    private void enqueueListBooks() {
        inputQueue.add("1");
    }

    private void enqueueListMovies() {
        inputQueue.add("2");
    }

    private void enqueueCheckoutMovie(String movie) {
        inputQueue.add("");
    }

    private void enqueueLogin(String libraryNumber, String password) {
        inputQueue.add("3");
        inputQueue.add(libraryNumber);
        inputQueue.add(password);

    }

    private void enqueueCheckoutBook(String book) {
        inputQueue.add("4");
        inputQueue.add(book);
    }

    private void enqueueReturnBook(String book) {
        inputQueue.add("6");
        inputQueue.add(book);
    }

    private void executeQueue() throws Exception {
        if (inputQueue == null || inputQueue.size() == 0) {
            when(io.readLine()).thenThrow(new IOException());
            libraryManager.execute();
            return;
        }

        String firstCommand = inputQueue.get(0);
        inputQueue.remove(0);
        OngoingStubbing<String> stubbing = when(io.readLine()).thenReturn(firstCommand);
        for (String command : inputQueue) {
            stubbing = stubbing.thenReturn(command);
        }
        stubbing.thenThrow(new IOException());
        libraryManager.execute();
    }

}
